package org.budo.dubbo.protocol.http;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.CollectionUtils;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcResult;
import com.alibaba.dubbo.rpc.service.GenericService;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
import org.budo.dubbo.protocol.http.cipher.CipherService;
import org.budo.dubbo.protocol.http.exception.BudoRuntimeException;
import org.budo.dubbo.protocol.http.exception.consumer.ConsumerSideRuntimeException;
import org.budo.dubbo.protocol.http.exception.remoting.RemotingRuntimeException;
import org.budo.dubbo.protocol.http.invocation.adapter.impl.DubboRpcInvocationAdapter;
import org.budo.dubbo.protocol.http.sign.SignCheckService;
import org.budo.dubbo.protocol.http.sign.SignEntity;
import org.budo.dubbo.protocol.http.util.BudoBeanUtil;
import org.budo.dubbo.protocol.http.util.BudoReflectionUtil;
import org.budo.dubbo.protocol.http.view.render.impl.DubboViewRender;
import org.budo.support.dao.page.PageModel;
import org.budo.support.lang.util.NumberUtil;
import org.budo.support.lang.util.ReflectUtil;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * @author lmw
 */
@Getter
@Setter
public abstract class AbstractBudoDubboHttpInvoker<T> implements Invoker<T> {
    private static final Logger log = LoggerFactory.getLogger(AbstractBudoDubboHttpInvoker.class);

    private static final boolean HAS_BUDO_PAGE_MODEL = ReflectUtil.hasClass("org.budo.support.dao.page.PageModel");

    private Class<T> interfaceType;

    private URL url;

    public void setInterface(Class<T> interfaceType) {
        this.interfaceType = interfaceType;
    }

    @Override
    public Class<T> getInterface() {
        return this.interfaceType;
    }

    public boolean isAvailable() {
        return true;
    }

    public void destroy() {
        log.info("destroy(), this=" + this);
    }

    protected String buildRequestUrl() {
        int port = this.getUrl().getPort();
        port = port < 1 ? 80 : port;

        if (!GenericService.class.equals(this.getInterface())) {
            return "http" + "://" + this.getUrl().getHost() + ":" + port + "/" + this.getUrl().getPath();
        }

        String _interface = this.getUrl().getParameter("interface");
        return "http" + "://" + this.getUrl().getHost() + ":" + port + "/api/" + _interface; // TODO
    }

    protected SignEntity signHeaders(Invocation invocation) {
        SignCheckService signCheckService = (SignCheckService) BudoBeanUtil.getBeanCached(SignCheckService.class, null);

        if (null == signCheckService) {
            String message = "#92 signCheckService required, invocation=" + invocation + ", this=" + this;
            // throw new ConsumerSideRuntimeException(message);
            log.error(message);
            return null;
        }

        return signCheckService.sign(invocation);
    }

    protected CipherService cipherService(String beanName) {
        CipherService cipherService = (CipherService) BudoBeanUtil.getBeanCached(null, beanName);

        if (null == cipherService) {
            throw new ConsumerSideRuntimeException("#100 cipherService required, beanName=" + beanName + ", this=" + this);
        }

        return cipherService;
    }

    /**
     * 公共的请求头
     */
    protected Map<String, String> commonHeaders(String serializationKey, Invocation invocation) {
        String _interface;
        if (GenericService.class.equals(this.getInterface())) {
            _interface = this.getUrl().getParameter("interface");
        } else {
            _interface = this.getInterface().getName();
        }

        Map<String, String> commonHeaders = CollectionUtils.toMap(Consts.HeaderKey.InterfaceName, _interface, //
                Consts.HeaderKey.MethodName, invocation.getMethodName(), //
                Consts.HeaderKey.Serialization_2, serializationKey, //
                Consts.HeaderKey.ViewRender, DubboViewRender.class.getName(), //
                Consts.HeaderKey.InvocationAdapter, DubboRpcInvocationAdapter.class.getName(), //
                Consts.HeaderKey.ContentType, Consts.ApplicationJson);

        return commonHeaders;
    }

    /**
     * 返回 resultValue 或抛出异常
     */
    protected Result processResult(Object resultEntity, Invocation invocation) throws Throwable {
        if (null == resultEntity) {
            String message = "#120 processResult result is null, invocation = " + invocation;
            throw new RemotingRuntimeException(message);
        }

        if (resultEntity instanceof Throwable) {
            throw (Throwable) resultEntity;
        }

        if (!(resultEntity instanceof Result)) {
            String message = "#134 processResult result is other type, type=" + resultEntity.getClass() + ", result=" + resultEntity;
            throw new RemotingRuntimeException(message);
        }

        Result result = (Result) resultEntity;
        Object resultValue = result.getValue();

        // 返回值为空，直接返回
        if (null == resultValue) {
            return result;
        }

        Class<?> matchedMethodReturnType = null;

        String _interface = this.getInterface().getName();
        String methodName = invocation.getMethodName();

        if (GenericService.class.equals(this.getInterface())) {
            matchedMethodReturnType = Map.class;
        } else {
            Method matchedMethod = BudoReflectionUtil.getMatchedMethodCached(_interface, //
                    methodName); // 已缓存
            matchedMethodReturnType = matchedMethod.getReturnType();
        }

        // 返回值类型匹配成功，返回
        if (BudoReflectionUtil.isArgumentValueTypeMatchParameterType(matchedMethodReturnType, resultValue.getClass())) {
            return this.processGenericResult(result, invocation, matchedMethodReturnType);
        }

        // 返回值为 JSONObject，转为对象再返回
        if (resultValue instanceof JSONObject) {
            JSONObject jsonObject = (JSONObject) resultValue;
            Object resultValueJavaObject = this.jsonObjectToJavaObject(jsonObject, matchedMethodReturnType, invocation);

            ((RpcResult) result).setValue(resultValueJavaObject);
            return this.processGenericResult(result, invocation, matchedMethodReturnType);
        }

        if (resultValue instanceof JSONArray) {
            return this.processGenericResult(result, invocation, matchedMethodReturnType);
        }

        String message = "#175 processResult error, result type miss match, result=" + result //
                + ", resultValue.type=" + resultValue.getClass() //
                + ", matchedMethod=" + _interface + "." + methodName;
        throw new RemotingRuntimeException(message);
    }

    private Object jsonObjectToJavaObject(JSONObject jsonObject, Class<?> clazz, Invocation invocation) {
        try {
            return jsonObject.toJavaObject(clazz);
        } catch (Throwable e) {
            throw new RuntimeException("#173 e=" + e + ", clazz=" + clazz + ", jsonObject=" + jsonObject + ", invocation=" + invocation, e);
        }
    }

    /**
     * 处理返回值中的泛型
     *
     * @see org.budo.dubbo.protocol.http.util.BudoReflectionUtil#getComponentType(Method,Integer)
     */
    private Result processGenericResult(Result result, Invocation invocation, Class<?> methodReturnType) {
        if (null == result || null == result.getValue()) {
            return result;
        }

        Object resultValue = this.processGenericResultValue(result.getValue(), invocation, methodReturnType);

        RpcResult rpcResult = new RpcResult(resultValue);
        rpcResult.setAttachments(result.getAttachments());
        return rpcResult;
    }

    /**
     * 这里只处理了返回值是 PageModel List Set 的泛型类型丢失问题，其他泛型未处理
     */
    private Object processGenericResultValue(Object value, Invocation invocation, Class<?> methodReturnType) {
        if (HAS_BUDO_PAGE_MODEL && value instanceof PageModel) {
            return this.processGenericPageModel((PageModel) value, invocation);
        }

        if (value instanceof List) { // JSONArray，可能需要转成Array
            return this.processGenericListOrArray(value, invocation);
        }

        if (value instanceof Set) {
            return this.processGenericSet((Set) value, invocation);
        }

        if (value instanceof Map) {
            return this.processGenericMap((Map) value, invocation);
        }

        // BigDecimal to Double
        if (ReflectUtil.isNumberType(methodReturnType)) {
            return NumberUtil.toNumber(value, methodReturnType);
        }

        return value;
    }

    private Object processGenericListOrArray(Object value, Invocation invocation) {
        List processGenericList = this.processGenericList((List) value, invocation);

        // 方法返回值不是数组，是集合
        boolean returnTypeIsArray = this.returnTypeIsArray(invocation);
        if (!returnTypeIsArray) {
            return processGenericList;
        }

        // 方法返回值是数组
        Class<?> returnTypeComponentType = (Class<?>) this.getReturnTypeComponentType(invocation);
        return processGenericList.toArray((Object[]) Array.newInstance(returnTypeComponentType, processGenericList.size()));
    }

    private Object processGenericPageModel(PageModel pageModel, Invocation invocation) {
        List list = pageModel.getList();
        if (null == list || list.isEmpty()) {
            return pageModel;
        }

        List processedList = this.processGenericList(list, invocation);
        return new PageModel(processedList, pageModel.getRecordCount());
    }

    private Set processGenericSet(Set set, Invocation invocation) {
        return (Set) this.processGenericCollection(set, invocation, new HashSet());
    }

    private List processGenericList(List list, Invocation invocation) {
        return (List) this.processGenericCollection(list, invocation, new ArrayList());
    }

    /**
     * 返回值是数组
     */
    private boolean returnTypeIsArray(Invocation invocation) {
        Class<?> interfaceType = invocation.getInvoker().getInterface();
        if (GenericService.class.equals(interfaceType)) {
            return false; // 这里不对
        }

        String typeName = interfaceType.getName();
        String methodName = invocation.getMethodName();
        Method invocationMethod = BudoReflectionUtil.getMatchedMethodCached(typeName, methodName);

        Type genericReturnType = invocationMethod.getGenericReturnType();
        return genericReturnType instanceof Class && ((Class) genericReturnType).isArray();
    }

    /**
     * 获得返回值(数组或集合)的元素类型
     */
    private Type getReturnTypeComponentType(Invocation invocation) {
        Class<?> interfaceType = invocation.getInvoker().getInterface();
        if (GenericService.class.equals(interfaceType)) {
            return Map.class;
        }

        String typeName = interfaceType.getName();
        String methodName = invocation.getMethodName();
        Method invocationMethod = BudoReflectionUtil.getMatchedMethodCached(typeName, methodName);

        Type genericReturnType = invocationMethod.getGenericReturnType();

        // 这里可能是Collection，也可能是数组
        if (genericReturnType instanceof Class && ((Class) genericReturnType).isArray()) {
            return ((Class) genericReturnType).getComponentType();
        }

        ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;
        Type genericType = parameterizedType.getActualTypeArguments()[0]; // TODO 这里直接取的方法返回类型的第一个泛型类型，不一定是正确的
        return genericType;
    }

    private Collection processGenericCollection(Collection src, Invocation invocation, Collection dest) {
        Type genericType = this.getReturnTypeComponentType(invocation);

        for (Object each : src) {
            if (null == each) {
                continue;
            }

            if (each instanceof JSONObject) {
                Object javaObject = ((JSONObject) each).toJavaObject(genericType);
                dest.add(javaObject); // JSONObject 转对象
                continue;
            }

            if (each instanceof JSONArray) {
                Object javaObject = ((JSONArray) each).toJavaObject(genericType);
                dest.add(javaObject); // JSONArray 转对象
                continue;
            }

            if (ReflectUtil.isBasicType(each.getClass())) {
                dest.add(each); // 基本类型
                continue;
            }

            String message = "#309 each=" + each + ", type=" + each.getClass();
            throw new BudoRuntimeException(message);
        }

        return dest;
    }

    private Map processGenericMap(Map map, Invocation invocation) {
        if (null == map || map.isEmpty()) {
            return map;
        }

        String typeName = invocation.getInvoker().getInterface().getName();
        String methodName = invocation.getMethodName();
        Method invocationMethod = BudoReflectionUtil.getMatchedMethodCached(typeName, methodName);

        Type genericReturnType = null != invocationMethod ? invocationMethod.getGenericReturnType() : null;
        if (!(genericReturnType instanceof ParameterizedType)) { // 接口的方法声明上未指定Map的泛型
            return map;
        }

        ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;

        Type keyType = parameterizedType.getActualTypeArguments()[0]; // TODO这里直接取的方法返回类型的第一个泛型类型，不一定是正确的
        Type valueType = parameterizedType.getActualTypeArguments()[1]; // TODO这里直接取的方法返回类型的第一个泛型类型，不一定是正确的

        Map result = new HashMap();
        Set entrySet = map.entrySet();
        for (Object each : entrySet) {
            Entry entry = (Entry) each;

            Object key = entry.getKey();
            Object value = entry.getValue();

            Object processedKey = this.processMapKey(key, keyType);

            // Map.Value 是 数组
            Object processedValue = this.processJsonArray(value, valueType);
            result.put(processedKey, processedValue);
        }
        return result;
    }

    private Object processMapKey(Object key, Type keyType) {
        if (null == key || null == keyType) {
            return key;
        }

        if (key.getClass().equals(keyType)) {
            return key;
        }

        if (keyType.equals(Integer.class)) {
            return null == key ? null : new Integer(key.toString());
        }

        String message = "#368 key=" + key + ", key.type=" + key.getClass() + ", keyType=" + keyType;
        throw new BudoRuntimeException(message);
    }

    private Object processJsonArray(Object value, Type valueType) {
        if (null == value || !(value instanceof JSONArray)) {
            return value;
        }

        JSONArray jsonArray = (JSONArray) value;
        Class valueClass = (Class) valueType;
        if (!valueClass.isArray()) {
            return value;
        }

        Class componentType = valueClass.getComponentType();
        Object[] result = (Object[]) Array.newInstance(componentType, jsonArray.size());
        for (int i = 0; i < result.length; i++) {
            result[i] = jsonArray.get(i);
        }
        return result;
    }
}
